package pr.lanmu.config.interceptor;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import pr.lanmu.AppApplication;
import pr.lanmu.common.po.LowCodePermission;
import pr.lanmu.common.po.ModulePermission;
import pr.lanmu.common.po.Permission;
import pr.lanmu.common.po.RoleModule;
import pr.lanmu.config.base.UserBase;
import pr.lanmu.config.constant.Code;
import pr.lanmu.config.function.MyConsumer;
import pr.lanmu.config.util.*;
import pr.lanmu.config.web.CustomException;
import pr.lanmu.config.web.ExceptionController;
import pr.lanmu.config.web.ValidateException;
import pr.lanmu.service.ApiService;

import javax.script.ScriptEngine;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;


/**
 * PC认证拦截器
 */
@Configuration
@SuppressWarnings("all")
public class AuthenticationInterceptor implements HandlerInterceptor {
    private final static List<String> METHODS = Arrays.asList("get", "post", "put", "delete");
    private static final String TOKEN = "token";
    private static final String LOG_INFO = "请求路径: {}";

    @Autowired
    private ApiService apiService;
    @Autowired
    private JwtUtil jwtUtil;
    private final static MyConsumer<HttpServletRequest> log = (req) -> Log.info(LOG_INFO, req.getRequestURI());

    /**
     * 配置鉴权拦截器
     *
     * @param request  .
     * @param response .
     * @param handler  .
     * @return .
     */
    @SneakyThrows
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        try {
            //获取请求路径
            String requestURI = request.getRequestURI();
            Optional<Permission> permissionOptional = new Permission().where(Permission::getUrl).eq(requestURI.replace("//", "/")
                    .substring(1)).oneOpt();
            //未被记录的路径直接放行
            if (permissionOptional.isPresent()) {
                Permission permission = permissionOptional.get();
                //判断是否有子节点接口，有的话代表是接口组，直接404
                new Permission().where(Permission::getPid).eq(permission.getId())
                        .limit(1)
                        .oneOpt()
                        .ifPresent(e -> {
                            throw Code.getException(Code.NOT_FOUND_EXCEPTION);
                        });
                //判断是否为低代码接口
                if (permission.getIsLowCode()) {
                    dealLowCode(permission, request, response);
                    return false;
                } else {
                    //非低代码,判断是否开放,开放直接放行
                    if (permission.getOpen()) return true;
                }
                authentication(permission, request);
                log.accept(request);
            }
            return true;
        } catch (Exception e) {
            //todo 由于springboot的异常处理机制需要有处理器，才会捕获处理异常，所以这里需要手动处理异常
            Res<?> res = new ExceptionController().handleException(e);
            sendRes(response, res);
            return false;
        }
    }

    /**
     * 处理低代码接口
     *
     * @param permission .
     * @param request    .
     * @param response   .
     */
    @SneakyThrows
    private void dealLowCode(Permission permission, HttpServletRequest request, HttpServletResponse response) {
        //查询低代码接口信息
        LowCodePermission lowCodePermission = new LowCodePermission().where(LowCodePermission::getPermissionId).eq(permission.getId())
                .oneOpt()
                .orElseThrow(() -> Code.getException(Code.NOT_FOUND_EXCEPTION));
        //检验成功,未开放,鉴权
        if (!permission.getOpen()) authentication(permission, request);
        AuthenticationInterceptor.log.accept(request);
        //获取请求参数
        ServletInputStream inputStream = request.getInputStream();
        String param = IoUtil.read(inputStream, StandardCharsets.UTF_8);
        //入参校验
        checkParam(lowCodePermission, param);
        //获取请求参数
        String json = lowCodePermission.getJson();
        String requestURI = request.getRequestURI();
        String method = requestURI.substring(requestURI.lastIndexOf("/") + 1)
                .toLowerCase(Locale.ROOT);
        //替换参数
        request.setAttribute("req", JSONUtil.toJsonStr(param));
        String params = replaceValue(json, request, response);
        if (METHODS.contains(method)) {
            Res<?> res = apiService.json(method, params, request.getSession());
            //替换返回值
            String responseBase = Optional.ofNullable(lowCodePermission.getResponse())
                    .orElseThrow(() -> new CustomException("不存在返回值配置"));
            request.setAttribute("res", res.getBody());
            sendRes(response, Res.builder()
                    .code(res.getCode())
                    .body(JSONUtil.parse(replaceValue(responseBase, request, response)))
                    .build());
        } else throw Code.getException(Code.NOT_MISMATCH_EXCEPTION);
    }

    /**
     * 替换参数
     *
     * @param json     .
     * @param request  .
     * @param response .
     * @return .
     */
    private String replaceValue(String json, HttpServletRequest request, HttpServletResponse response) {
        try {
            ScriptEngine context = AppApplication.scriptEngine.get();
            //使用js给param定义变量
            loadData(context, request);
            //使用js给json定义变量
            //将json取出
            return context.eval("JSON.stringify(" + json + ")").toString();
        } catch (Exception e) {
            Log.error("JS引擎执行错误：{}", e);
            sendRes(response, Res.fail("规则填写错误"));
            throw new CustomException("规则填写错误");
        }
    }

    /**
     * 加载数据
     *
     * @param js      .
     * @param request .
     */
    @SneakyThrows
    private void loadData(ScriptEngine js, HttpServletRequest request) {
        //使用js转对象
        js.eval("var req = " + JSONUtil.toJsonStr(request.getAttribute("req")));
        js.eval("var res = " + JSONUtil.toJsonStr((request.getAttribute("res") == null ? "{}" : request.getAttribute("res"))));
        js.eval("var system = " + JSONUtil.toJsonStr(Current.getUser()));
    }

    /**
     * 低代码入参校验
     *
     * @param lowCodePermission .
     * @param param             .
     */
    @SneakyThrows
    private void checkParam(LowCodePermission lowCodePermission, String param) {
        ArrayList<String> errors = new ArrayList<>();
        ValidUtil.valid(lowCodePermission.getParam(), lowCodePermission.getParamValid(), param, errors);
        if (!errors.isEmpty()) {
            Log.warn("参数错误：{}", errors);
            throw new ValidateException(errors.getFirst());
        }
    }

    /**
     * 鉴权
     *
     * @param permission .
     */
    private void authentication(Permission permission, HttpServletRequest req) {
        //获取当前用户
        UserBase userBase = getUser(req);
        //判断是否登录
        if (Objects.isNull(userBase)) throw Code.getException(Code.NO_LOGIN_EXCEPTION);
        //超管直接放行  无需鉴权直接放行
        if (userBase.getAdmin() || !permission.getAuthentication()) return;
        //获取包含权限的模块
        List<Long> moduleIds = new ModulePermission().where(ModulePermission::getPermissionId).eq(permission.getId())
                .list()
                .stream()
                .map(ModulePermission::getModuleId)
                .collect(Collectors.toList());
        //获取用户角色,把逗号分隔变成List
        if (!StrUtil.isBlankOrUndefined(userBase.getRoleId())) {
            List<Long> roleIds = Arrays.stream(userBase.getRoleId()
                            .split(","))
                    .map(Long::parseLong)
                    .collect(Collectors.toList());
            if (moduleIds.isEmpty() || roleIds.isEmpty()) throw Code.getException(Code.NO_PERMISSION_EXCEPTION);
            //查询角色包含的模块
            new RoleModule().where(RoleModule::getRoleId).in(roleIds)
                    .and(RoleModule::getModuleId).in(moduleIds)
                    .limit(1)
                    .oneOpt()
                    .orElseThrow(() -> Code.getException(Code.NO_PERMISSION_EXCEPTION));
        } else throw Code.getException(Code.NO_PERMISSION_EXCEPTION);
    }

    private UserBase getUser(HttpServletRequest req) {
        UserBase userBase = Current.getUser();
        if (userBase == null) {
            String token = req.getHeader(TOKEN);
            //线程局部变量植入用户信息
            if (Objects.nonNull(token)) {
                userBase = jwtUtil.verifierJwtInfo(token);
                Current.setUser(userBase);
            }
        }
        return userBase;
    }

    /**
     * 发送响应
     *
     * @param response .
     * @param res      .
     */
    @SneakyThrows
    private void sendRes(HttpServletResponse response, Object res) {
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write(JSONUtil.toJsonStr(res));
        IoUtil.close(writer);
    }
}
